#!/usr/bin/env python3
#
# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Migrate /config/auth certificates and keys into PKI configuration

import os

from sys import argv
from sys import exit

from vyos.configtree import ConfigTree
from vyos.pki import load_certificate
from vyos.pki import load_crl
from vyos.pki import load_private_key
from vyos.pki import encode_certificate
from vyos.pki import encode_private_key
from vyos.utils.process import run

if len(argv) < 2:
    print("Must specify file name!")
    exit(1)

file_name = argv[1]

with open(file_name, 'r') as f:
    config_file = f.read()

pki_base = ['pki']
ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer']

config = ConfigTree(config_file)
changes_made = False

AUTH_DIR = '/config/auth'

def wrapped_pem_to_config_value(pem):
    return "".join(pem.strip().split("\n")[1:-1])

if config.exists(ipsec_site_base):
    config.set(pki_base + ['ca'])
    config.set_tag(pki_base + ['ca'])

    config.set(pki_base + ['certificate'])
    config.set_tag(pki_base + ['certificate'])

    for peer in config.list_nodes(ipsec_site_base):
        if not config.exists(ipsec_site_base + [peer, 'authentication', 'x509']):
            continue

        changes_made = True

        peer_x509_base = ipsec_site_base + [peer, 'authentication', 'x509']
        pki_name = 'peer_' + peer.replace(".", "-").replace("@", "")

        if config.exists(peer_x509_base + ['cert-file']):
            cert_file = config.return_value(peer_x509_base + ['cert-file'])
            cert_path = os.path.join(AUTH_DIR, cert_file)
            cert = None

            if os.path.isfile(cert_path):
                if not os.access(cert_path, os.R_OK):
                    run(f'sudo chmod 644 {cert_path}')

                with open(cert_path, 'r') as f:
                    cert_data = f.read()
                    cert = load_certificate(cert_data, wrap_tags=False)

            if cert:
                cert_pem = encode_certificate(cert)
                config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
                config.set(peer_x509_base + ['certificate'], value=pki_name)
            else:
                print(f'Failed to migrate certificate on peer "{peer}"')

            config.delete(peer_x509_base + ['cert-file'])

        if config.exists(peer_x509_base + ['ca-cert-file']):
            ca_cert_file = config.return_value(peer_x509_base + ['ca-cert-file'])
            ca_cert_path = os.path.join(AUTH_DIR, ca_cert_file)
            ca_cert = None

            if os.path.isfile(ca_cert_path):
                if not os.access(ca_cert_path, os.R_OK):
                    run(f'sudo chmod 644 {ca_cert_path}')

                with open(ca_cert_path, 'r') as f:
                    ca_cert_data = f.read()
                    ca_cert = load_certificate(ca_cert_data, wrap_tags=False)

            if ca_cert:
                ca_cert_pem = encode_certificate(ca_cert)
                config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(ca_cert_pem))
                config.set(peer_x509_base + ['ca-certificate'], value=pki_name)
            else:
                print(f'Failed to migrate CA certificate on peer "{peer}"')

            config.delete(peer_x509_base + ['ca-cert-file'])

        if config.exists(peer_x509_base + ['crl-file']):
            crl_file = config.return_value(peer_x509_base + ['crl-file'])
            crl_path = os.path.join(AUTH_DIR, crl_file)
            crl = None

            if os.path.isfile(crl_path):
                if not os.access(crl_path, os.R_OK):
                    run(f'sudo chmod 644 {crl_path}')

                with open(crl_path, 'r') as f:
                    crl_data = f.read()
                    crl = load_crl(crl_data, wrap_tags=False)

            if crl:
                crl_pem = encode_certificate(crl)
                config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
            else:
                print(f'Failed to migrate CRL on peer "{peer}"')

            config.delete(peer_x509_base + ['crl-file'])

        if config.exists(peer_x509_base + ['key', 'file']):
            key_file = config.return_value(peer_x509_base + ['key', 'file'])
            key_passphrase = None

            if config.exists(peer_x509_base + ['key', 'password']):
                key_passphrase = config.return_value(peer_x509_base + ['key', 'password'])

            key_path = os.path.join(AUTH_DIR, key_file)
            key = None

            if os.path.isfile(key_path):
                if not os.access(key_path, os.R_OK):
                    run(f'sudo chmod 644 {key_path}')

                with open(key_path, 'r') as f:
                    key_data = f.read()
                    key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False)

            if key:
                key_pem = encode_private_key(key, passphrase=key_passphrase)
                config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))

                if key_passphrase:
                    config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected'])
                    config.set(peer_x509_base + ['private-key-passphrase'], value=key_passphrase)
            else:
                print(f'Failed to migrate private key on peer "{peer}"')

            config.delete(peer_x509_base + ['key'])

if changes_made:
    try:
        with open(file_name, 'w') as f:
            f.write(config.to_string())
    except OSError as e:
        print("Failed to save the modified config: {}".format(e))
        sys.exit(1)
